#ifndef SERVER_H_
#define SERVER_H_

namespace mtm {

/************ DO NOT CHANGE ************/
class Server;

}
/************ DO NOT CHANGE ************/

#include <vector>
#include <iostream>
#include <memory>
#include "priority_queue.h"
#include "mtm_ex5.h"
#include "user.h"
#include "group.h"
#include "recipe.h"

using std::vector;
using std::shared_ptr;
using std::cout;
using std::endl;

namespace mtm {

/*
 * A class that represents server.
 */
class Server {
	static shared_ptr<Server> instance;
	/************ add your members here ************/
	//connected users, sorted by id
	PriorityQueue<unsigned, shared_ptr<User>> users;
	vector<shared_ptr<Group>> groups;

	/*
	 * get a pointer to a connected user
	 * arguments:
	 * userId - the id of the user
	 * return value: a pointer to the user
	 * throws:
	 * UserNotConnectedException, if the user is not connected
	 */
	shared_ptr<User>& getUser(unsigned userId);
	/*
	 * get a pointer to a group
	 * arguments:
	 * name - the group's name
	 * return value: a pointer to the group
	 * throws:
	 * NoSuchGroupException, if there is not such a group
	 */
	shared_ptr<Group>& getGroup(const string& name);

public:
	/************ DO NOT CHANGE ************/
	static shared_ptr<Server>& getInstance() {
		return instance;
	}

	static void reset() {
		instance.reset();
		instance = std::make_shared<Server>();
	}
	/************ DO NOT CHANGE ************/

	/*
	 * connect - user connects to server
	 * arguments:
	 * user - pointer to the connecting user
	 * throws:
	 * IllegalArguments, if user is NULL
	 * UserAlreadyConnectedException, if user is already connected
	 */
	void connect(shared_ptr<User>& user);

	/* disconnect - user disconnent from sever
	 * arguments:
	 * user - pointer to the disconnecting user
	 * throws:
	 * IllegalArguments, if user is NULL
	 * UserNotConnectedException, if user is not connected
	 */
	void disconnect(shared_ptr<User>& user);
	/*
	 * prints the connected users, by id (high to low)
	 * arguments:
	 * os - the stream to print the users to
	 * return value: os
	 */
	ostream& printUsersList(ostream& os);
	/*
	 * prints the groups, by activity level (high to low)
	 * arguments:
	 * os - the stream to print the users to
	 * return value: os
	 */
	ostream& printMostActiveGroups(ostream& os);
	/*
	 * clean old recipes, so no group has more than k recipes
	 * arguments:
	 * k - max recipes in group after cleaning
	 * throws:
	 * IllegalArguments, if k is negative
	 */
	void cleanMemory(int k);

	/************ these functions can be useful ************/
	//helper function for User::openNewGroup
	shared_ptr<PowerUser> openNewGroup(unsigned int userId,
			const string& groupName);

	//helper function for User::joinNewGroup
	void requestToJoinGroup(unsigned int userId, const string& groupName);

	//helper function for User::leaveGroup
	void requestToLeaveGroup(unsigned int userId, const string& groupName);

	/************ add more function if you wish ************/

	/*
	 * validate that the user is connected, and is in a group
	 * arguments:
	 * userId - the id of the user
	 * throws:
	 * UserNotConnectedException, if the user is not connected
	 * NotInGroupException, if the user is not in a group
	 */
	void validateUserGroup(unsigned userId);
	/*
	 * turn user into a leader
	 * (if the user is connected, also replace its inner pointer to
	 * point on the new leader)
	 * arguments:
	 * user - pointer to the user which will became leader
	 * return value: a pointer to the new leader
	 */
	shared_ptr<PowerUser> makeLeader(const shared_ptr<User>& user);
};

}
#endif /* SERVER_H_ */
